use super::{
    err, err_value, json, ok, throw_err, Base, Client, Collect, ECode, KeyNode, Lock, MapSv, Resp,
    SvCache, VBase, VMap, Value,
};
use std::collections::HashSet;

pub struct Psn;

impl Base for Psn {}

impl Psn {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        client: &Client,
        cache: SvCache,
        lock: Lock,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        let collect = Collect::new(client, cache, line);

        match cmd {
            "list" => {
                let task_sn = data.ck_str("k")?;
                let k_sn = collect.key_sub_sn("task", "psn", task_sn)?;

                let ret = collect.list_array(&k_sn, "psn", None).await?;
                ok!(ret)
            }
            "mmd" => {
                let _ = lock.lock().await;

                collect.md_item_set("psn", para).await?;

                ok!()
            }
            "md" => {
                let _ = lock.lock().await;

                let sn = data.ck_str_empty_trim("k")?;
                let val = data.ck_val("v")?;

                collect.md_item("psn", sn, val).await?;

                ok!()
            }
            "rmitem" => {
                let val = data.ck_array("v")?;
                let psn = data.ck_str("k")?;
                let k_psn = collect.key_sn("psn", psn)?;

                let mut put = Vec::new();
                collect.rm_field(&k_psn, &val, &mut put)?;

                collect.commit(Some(&put), None).await?;

                ok!()
            }
            "get" => {
                let sn = data.ck_str("k")?;
                let ret = collect.item_sn("psn", sn).await?;

                ok!(ret)
            }
            "rm" => self.do_rm(data, lock, &collect).await,
            "add" => self.do_add(data, lock, &collect).await,
            _ => throw_err!(ECode::NoServe, "no psn serve"),
        }
    }

    async fn do_rm(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;
        let k_psn = collect.key_sn("psn", psn)?;

        let body = match collect.item(&k_psn).await {
            Ok(v) => v,
            Err(_) => {
                return ok!();
            }
        };

        let task_sn = body.ck_str("task")?;
        let k_task = collect.key_sub_sn("task", "psn", &task_sn)?;

        let mut put = Vec::new();
        let mut del = vec![k_psn.to_string()];

        let mut psn_set = HashSet::new();
        psn_set.insert(psn.to_string());

        collect.del_mass_item(&k_task, psn, &mut put, &mut del);

        let _ = collect.del_station_psn(&psn_set, &mut put, &mut del).await;

        collect.commit(Some(&put), Some(&del)).await?;

        ok!()
    }

    async fn do_add(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let task_sn = data.ck_str_empty_trim("k")?;
        let val = data.ck_array("v")?;

        let k_task = collect.key_sub_sn("task", "psn", &task_sn)?;

        let _ = lock.lock().await;

        let task = collect.item_sn("task", &task_sn).await?;
        let pnum = task.ck_i64("pnum")?;
        let tp = match task.k_str("tp") {
            Some(v) => v,
            None => "",
        };

        if let Ok(body) = collect.content(&k_task).await {
            let num = body.len() + val.len();
            if num as i64 > pnum {
                throw_err!(ECode::Para, "psn count must le pnum");
            }
        }

        let mut psn_vec = Vec::new();

        for d in val.iter() {
            let sn = d.ck_str("sn")?;
            if sn.is_empty() {
                continue;
            }

            psn_vec.push(json!(sn));

            let k_psn = collect.key_sn("psn", sn)?;

            if let Ok(_) = collect.item(&k_psn).await {
                throw_err!(ECode::Psn, format!("psn repeat: {sn}"));
            }
        }

        let mut put = Vec::new();
        collect.merge_mass_vec(&k_task, &psn_vec, &mut put)?;

        for d in val.iter() {
            let mut cmd = d.ckobj()?.clone();

            cmd.insert("task".to_string(), json!(task_sn));
            cmd.insert("tp".to_string(), json!(tp));

            let sn = cmd.ck_str("sn")?;
            let k_psn = collect.key_sn("psn", sn)?;

            put.push((k_psn, json!(cmd)));
        }

        collect.commit(Some(&put), None).await?;

        ok!()
    }
}
